﻿using System;
using System.ComponentModel;
using System.IO;
using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.ViewModel;
using MvvmOrm.Properties;
using MvvmOrm.Services;
using System.Data;

namespace MvvmOrm.Models
{
    public enum DbSystemEnum { Mysql = 0, Mssql = 1, Msaccess = 2, Pgsql = 3, Sqlite = 4 }
    public enum DatabaseItemTypeEnum { Table, View, Procedure }
    public enum DatabaseItemLogTypeEnum { Info, Warning, Error }
    public enum MsaccessSecurityType { None, Password, Workgroup }

    class MainViewModel : NotificationObject
    {
        public static string SanitizeName(string name)
        {
            string charsAllowed;
            string charsAllowedAsFirst;
            string sanitizedName = "";

            charsAllowedAsFirst = "abcdefghijklmnopqrstuvwxyz";
            charsAllowedAsFirst += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
            charsAllowedAsFirst += "_";

            charsAllowed = charsAllowedAsFirst + "0123456789";

            if (name.Length == 0)
                return sanitizedName;

            if (charsAllowedAsFirst.IndexOf(name[0]) < 0)
                sanitizedName += "_";

            foreach (char c in name)
                if (charsAllowed.IndexOf(c) < 0)
                    sanitizedName += "_";
                else
                    sanitizedName += c;

            return sanitizedName;
        }

        public static Type GetType(DbType dbType)
        {
            switch (dbType) {
            case System.Data.DbType.Byte:
                return typeof(System.Byte);
            case System.Data.DbType.SByte:
                return typeof(System.SByte);
            case System.Data.DbType.UInt16:
                return typeof(System.UInt16);
            case System.Data.DbType.UInt32:
                return typeof(System.UInt32);
            case System.Data.DbType.UInt64:
                return typeof(System.UInt64);
            case System.Data.DbType.Int16:
                return typeof(System.Int16);
            case System.Data.DbType.Int32:
                return typeof(System.Int32);
            case System.Data.DbType.Int64:
                return typeof(System.Int64);
            case System.Data.DbType.Decimal:
            case System.Data.DbType.Currency:
                return typeof(System.Decimal);
            case System.Data.DbType.Double:
                return typeof(System.Double);
            case System.Data.DbType.Single:
                return typeof(System.Single);
            case System.Data.DbType.String:
            case System.Data.DbType.StringFixedLength:
            case System.Data.DbType.AnsiString:
            case System.Data.DbType.AnsiStringFixedLength:
                return typeof(System.String);
            case System.Data.DbType.Boolean:
                return typeof(System.Boolean);
            case System.Data.DbType.Binary:
                return typeof(System.Byte[]);
            case System.Data.DbType.Date:
            case System.Data.DbType.DateTime:
            case System.Data.DbType.DateTime2:
                return typeof(System.DateTime);
            case System.Data.DbType.DateTimeOffset:
                return typeof(System.DateTimeOffset);
            case System.Data.DbType.Time:
                return typeof(System.TimeSpan);
            case System.Data.DbType.Object:
                return typeof(System.Object);
            case System.Data.DbType.Guid:
                return typeof(System.Guid);
            case System.Data.DbType.Xml:
                return typeof(System.String);
            }
            throw new Exception("unsupported column DB type");
        }

        public static string GetDefaultValueStringed(bool nullable, DbType dbType)
        {
            if (nullable)
                return "null";
            switch (dbType) {
            case System.Data.DbType.Binary:
                return "new System.Byte[0]";
            case System.Data.DbType.Boolean:
                return "default(System.Boolean)";
            case System.Data.DbType.Byte:
                return "default(System.Byte)";
            case System.Data.DbType.SByte:
                return "default(System.SByte)";
            case System.Data.DbType.Int16:
                return "default(System.Int16)";
            case System.Data.DbType.Int32:
                return "default(System.Int32)";
            case System.Data.DbType.Int64:
                return "default(System.Int64)";
            case System.Data.DbType.UInt16:
                return "default(System.UInt16)";
            case System.Data.DbType.UInt32:
                return "default(System.UInt32)";
            case System.Data.DbType.UInt64:
                return "default(System.UInt64)";
            case System.Data.DbType.String:
            case System.Data.DbType.StringFixedLength:
            case System.Data.DbType.AnsiString:
            case System.Data.DbType.AnsiStringFixedLength:
            case System.Data.DbType.Xml:
                return "String.Empty";
            case System.Data.DbType.Date:
            case System.Data.DbType.DateTime:
            case System.Data.DbType.DateTime2:
                return "default(System.DateTime)";
            case System.Data.DbType.DateTimeOffset:
                return "default(System.DateTimeOffset)";
            case System.Data.DbType.Decimal:
            case System.Data.DbType.Currency:
                return "default(System.Decimal)";
            case System.Data.DbType.Double:
                return "default(System.Double)";
            case System.Data.DbType.Single:
                return "default(System.Single)";
            case System.Data.DbType.Time:
                return "default(System.TimeSpan)";
            case System.Data.DbType.Object:
                return "new Object()";
            case System.Data.DbType.Guid:
                return "default(System.Guid)";
            }
            throw new Exception("unsupported column DB type");
        }

        public const string EXTENSION = "mo";
        public const string SCHEMA_VERSION = "1";

        DbConnection _conn;

        Dialogs Dialogs { get; set; }

        public DelegateCommand OpenNewCmd { get; private set; }
        public DelegateCommand<string> OpenCmd { get; private set; }
        public DelegateCommand<bool?> SaveCmd { get; private set; }
        public DelegateCommand CloseCmd { get; private set; }
        public DelegateCommand ResetSettingsCmd { get; private set; }
        public DelegateCommand SetDefaultDirectorySaveCmd { get; private set; }
        public DelegateCommand SetDefaultDirectoryOutputCmd { get; private set; }

        public MainViewModel()
        {
            Dialogs = new Dialogs();

            OpenNewCmd = new DelegateCommand(OpenNew, CanOpenNew);
            OpenCmd = new DelegateCommand<string>(Open, CanOpen);
            SaveCmd = new DelegateCommand<bool?>(Save, CanSave);
            CloseCmd = new DelegateCommand(Close, CanClose);
            ResetSettingsCmd = new DelegateCommand(ResetSettings);
            SetDefaultDirectorySaveCmd = new DelegateCommand(SetDefaultDirectorySave);
            SetDefaultDirectoryOutputCmd = new DelegateCommand(SetDefaultDirectoryOutput);

            Settings.Default.PropertyChanged += Settings_PropertyChanged;
        }

        void Settings_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Settings.Default.Save();
        }

        public string ViewTitle
        {
            get
            {
                if (!IsOpen)
                    return "MVVM ORM";
                if (IsNew)
                    return "<unsaved file> - MVVM ORM";
                return OpenedFile + " - MVVM ORM";
            }
        }

        string _openedFile;
        public string OpenedFile
        {
            get { return _openedFile; }
            private set
            {
                _openedFile = value;
                RaisePropertyChanged(() => ViewTitle);
                RaisePropertyChanged(() => IsOpen);
                OpenNewCmd.RaiseCanExecuteChanged();
                OpenCmd.RaiseCanExecuteChanged();
                SaveCmd.RaiseCanExecuteChanged();
                CloseCmd.RaiseCanExecuteChanged();
            }
        }
        public bool IsOpen
        {
            get { return !String.IsNullOrWhiteSpace(OpenedFile); }
        }

        public bool IsNew { get; private set; }

        public DatabaseModel.Row DB { get; private set; }

        bool HasUnsavedChanges
        {
            get { return ChangesCount() > ChangesCountSinceLastSave; }
        }

        long ChangesCountSinceLastSave { get; set; }

        void OpenNew()
        {
            IsNew = true;
            OpenedFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Path.GetRandomFileName() + "." + EXTENSION);
            _conn = new DbConnection(OpenedFile);
            _conn.Open();
            _conn.SetPragmas();
            _conn.ExecuteNonQuery(Resources.db_create);
            Version.Operation.Insert(_conn, new Version.Row(SCHEMA_VERSION));
            DatabaseModel.Operation.Insert(_conn, DatabaseModel.Row.GetNew(DbSystemEnum.Sqlite, Settings.Default.DefaultDirectoryOutput));
            Load();
        }
        bool CanOpenNew()
        {
            return !IsOpen;
        }

        void Open(string filename)
        {
            if (string.IsNullOrWhiteSpace(filename)) {
                if (Dialogs.OpenFile(out filename,
                        Settings.Default.DefaultDirectorySave,
                        "MVVM ORM file",
                        new[] { EXTENSION }) != DialogResponse.Ok)
                    return;
            }

            IsNew = false;
            OpenedFile = filename;
            _conn = new DbConnection(OpenedFile);
            _conn.Open();
            _conn.SetPragmas();
            Load();
        }
        bool CanOpen(string filename)
        {
            return !IsOpen;
        }

        void Close()
        {
            if (!IsOpen)
                throw new InvalidOperationException("Cannot close as no file is opened.");

            if (HasUnsavedChanges) {
                switch (Dialogs.AskQuestion("Do you want to save changes to [" + (IsNew ? "<unsaved file>" : OpenedFile) + "] before exiting?", "", true)) {
                case DialogResponse.Yes:
                    if (!DoSave(false))
                        return;
                    break;
                case DialogResponse.Cancel:
                    return;
                }
            }

            DB.Dispose();
            DB = null;
            _conn.RollbackTransaction();
            _conn.Close();
            _conn.Dispose();
            _conn = null;
            OpenedFile = "";
            RaisePropertyChanged(() => DB);
        }
        bool CanClose()
        {
            return IsOpen;
        }

        void Save(bool? saveAs)
        {
            DoSave(saveAs.Value);
        }
        bool CanSave(bool? saveAs)
        {
            return IsOpen && (saveAs.Value || HasUnsavedChanges);
        }

        void SetDefaultDirectorySave()
        {
            string path;
            if (Dialogs.OpenDirectory(out path, "Select default save directory", Settings.Default.DefaultDirectorySave, true)
                    != DialogResponse.Ok)
                return;
            Settings.Default.DefaultDirectorySave = path;
        }

        void SetDefaultDirectoryOutput()
        {
            string path;
            if (Dialogs.OpenDirectory(out path, "Select default output directory", Settings.Default.DefaultDirectoryOutput, true) != DialogResponse.Ok)
                return;
            Settings.Default.DefaultDirectoryOutput = path;
        }

        void ResetSettings()
        {
            Settings.Default.Reset();
        }

        bool DoSave(bool saveAs)
        {
            string filename = "";
            if (saveAs || IsNew) {

                string initialDirectory = "";
                if (!IsNew) {
                    initialDirectory = Path.GetDirectoryName(OpenedFile);
                    filename = Path.GetFileNameWithoutExtension(OpenedFile);
                }
                else {
                    initialDirectory = DB.OutputDirectory;
                    filename = DB.Namespace;
                }
                if (Dialogs.SaveFile(ref filename,
                        initialDirectory,
                        "MVVM ORM file",
                        new[] { EXTENSION }) != DialogResponse.Ok)
                    return false;
            }

            if (string.IsNullOrWhiteSpace(OpenedFile))
                throw new Exception("cannot save because save destination is empty");
            _conn.CommitTransaction();
            _conn.ExecuteNonQuery("PRAGMA wal_checkpoint");
            IsNew = false;
            if (!string.IsNullOrWhiteSpace(filename) && filename != OpenedFile) {
                System.IO.File.Copy(OpenedFile, filename, true);
                OpenedFile = filename;
                _conn.Close();
                _conn.Database = OpenedFile;
                _conn.Open();
                _conn.SetPragmas();
            }
            _conn.BeginTransaction();
            ChangesCountSinceLastSave = ChangesCount();
            return true;
        }

        void Load()
        {
            if (DB != null)
                throw new Exception("Previous Database Model was not disposed");
            DB = DatabaseModel.Operation.Select(_conn, 1);
            RaisePropertyChanged(() => DB);
            _conn.BeginTransaction();
            ChangesCountSinceLastSave = ChangesCount();
        }

        long ChangesCount()
        {
            return (long)_conn.ExecuteScalar("SELECT total_changes()");
        }
    }
}
